package com.xinchili.autoddl;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.validation.constraints.Size;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 自动根据实体类创建/修改数据库表结构，并支持字段和表注释。
 * 主要用于开发环境自动建表或同步字段变更，避免手动维护 SQL 脚本。
 */
@Component
public class TableAutoDdlByDemo {
    public static final Logger logger = LoggerFactory.getLogger(TableAutoDdlByDemo.class);

    /**
     * 定义 Java 类型与 SQL 数据类型的映射关系。
     * 可扩展添加更多类型支持（如 LocalDate、BigDecimal 等）。
     */
    public static final Map<Class<?>, String> SQL_TYPES = new HashMap<>();

    static {
        // 基础类型映射
        SQL_TYPES.put(String.class, "VARCHAR(255)");
        SQL_TYPES.put(int.class, "INT");
        SQL_TYPES.put(boolean.class, "TINYINT(1)");
        SQL_TYPES.put(Boolean.class, "TINYINT(1)");
        SQL_TYPES.put(Integer.class, "INT");
        SQL_TYPES.put(Long.class, "BIGINT");
        SQL_TYPES.put(Date.class, "DATETIME");
        SQL_TYPES.put(Double.class, "DOUBLE");
        SQL_TYPES.put(Float.class, "FLOAT");

        // 新增支持的复杂类型
        SQL_TYPES.put(java.time.LocalDate.class, "DATE");
        SQL_TYPES.put(java.time.LocalDateTime.class, "DATETIME");
        SQL_TYPES.put(java.math.BigDecimal.class, "DECIMAL(19,2)");
        SQL_TYPES.put(byte[].class, "BLOB");
        SQL_TYPES.put(Byte[].class, "BLOB");
        // 0 或 1 表示 boolean
        SQL_TYPES.put(char.class, "CHAR(1)");
        SQL_TYPES.put(Character.class, "CHAR(1)");
    }

    /**
     * 扫描实体类所在的包路径。
     * 需要与实际项目中的实体类路径一致。
     */
    public final String ENTITY_PACKAGE = "com.dx.front.biz.dsGpsc.dataStatistics.entity.pojo";

    @Autowired
    public JdbcTemplate jdbcTemplate;

    public TableAutoDdlByDemo() {
    }

    /**
     * 初始化方法，在 Bean 创建完成后自动执行。
     * 扫描指定包下的实体类并处理其对应的数据库表结构。
     */
    @PostConstruct
    public void init() {
        Set<Class<?>> entityClasses = scanEntityClasses();
        for (Class<?> clazz : entityClasses) {
            processEntityClass(clazz);
        }
    }

    /**
     * 扫描所有带有 @TableName 注解的类。
     *
     * @return 返回实体类集合
     */
    public Set<Class<?>> scanEntityClasses() {
        ClassPathScanningCandidateComponentProvider scanner =
                new ClassPathScanningCandidateComponentProvider(true);
        scanner.addIncludeFilter(new AnnotationTypeFilter(TableName.class));

        Set<Class<?>> entityClasses = new HashSet<>();
        for (org.springframework.beans.factory.config.BeanDefinition bd :
                scanner.findCandidateComponents(ENTITY_PACKAGE)) {
            try {
                Class<?> clazz = Class.forName(bd.getBeanClassName());
                entityClasses.add(clazz);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return entityClasses;
    }

    /**
     * 处理单个实体类，判断是否需要建表或修改表结构。
     *
     * @param clazz 实体类对象
     */
    void processEntityClass(Class<?> clazz) {
        TableName tableAnno = clazz.getAnnotation(TableName.class);
        if (tableAnno == null) return;

        String tableName = tableAnno.value();

        if (!tableExists(tableName)) {
            createTable(tableName, clazz);
        } else {
            alterTable(tableName, clazz);
        }
    }

    /**
     * 判断数据库中是否存在指定名称的表。
     *
     * @param tableName 表名
     * @return 是否存在
     */
    public boolean tableExists(String tableName) {
        String sql = "SHOW TABLES LIKE ?";
        List<String> result = jdbcTemplate.queryForList(sql, String.class, tableName);
        return !result.isEmpty();
    }

    /**
     * 获取类及其父类的所有字段。
     *
     * @param clazz 实体类
     * @return 字段列表
     */
    public List<Field> getAllFields(Class<?> clazz) {
        List<Field> fields = new ArrayList<>();
        while (clazz != null && clazz != Object.class) {
            Collections.addAll(fields, clazz.getDeclaredFields());
            clazz = clazz.getSuperclass();
        }
        return fields;
    }

    /**
     * 根据实体类创建数据库表。
     *
     * @param tableName 表名
     * @param clazz     实体类
     */
    public void createTable(String tableName, Class<?> clazz) {
        StringBuilder sb = new StringBuilder("CREATE TABLE ");
        sb.append(tableName).append(" (");

        List<Field> allFields = getAllFields(clazz);
        for (Field field : allFields) {
            String columnName = resolveColumnName(field);
            if (columnName == null) continue; // 忽略无效字段

            String sqlType = resolveSqlType(field);
            String comment = resolveColumnComment(field);

            sb.append(columnName).append(" ").append(sqlType);
            if (!comment.isEmpty()) {
                sb.append(" COMMENT '").append(comment).append("'");
            }
            sb.append(", ");
        }

        if (sb.toString().endsWith(", ")) {
            sb.setLength(sb.length() - 2); // 去掉最后的逗号空格
        }

        String tableComment = resolveTableComment(clazz);
        sb.append(") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC");
        if (!tableComment.isEmpty()) {
            sb.append(" COMMENT='").append(tableComment).append("'");
        }
        sb.append(";");

        logger.info("建表语句====>{}", sb);
        jdbcTemplate.execute(sb.toString());
    }

    /**
     * 修改数据库表结构（新增字段、修改字段类型/注释等）。
     *
     * @param tableName 表名
     * @param clazz     实体类
     */
    public void alterTable(String tableName, Class<?> clazz) {
        Set<String> existingColumns = getTableColumns(tableName);
        Map<String, String> columnTypes = getColumnTypes(tableName);
        Map<String, String> columnComments = getColumnComments(tableName);

        List<Field> allFields = getAllFields(clazz);
        for (Field field : allFields) {
            String columnName = resolveColumnName(field);
            if (columnName == null) continue;

            if (!existingColumns.contains(columnName.toLowerCase())) {
                // 新增字段
                String sqlType = resolveSqlType(field);
                String comment = resolveColumnComment(field);
                String sql = "ALTER TABLE " + tableName +
                        " ADD COLUMN " + columnName + " " + sqlType;
                if (!comment.isEmpty()) {
                    sql += " COMMENT '" + comment + "'";
                }
                sql += ";";
                jdbcTemplate.execute(sql);
                logger.info("新增字段====>{}", sql);
            } else {
                // 修改字段类型或注释
                String expectedType = resolveSqlType(field);
                String expectedComment = resolveColumnComment(field);
                String actualType = columnTypes.getOrDefault(columnName.toUpperCase(), "").toUpperCase();
                String actualComment = columnComments.getOrDefault(columnName.toUpperCase(), "");

                if (!expectedType.equals(actualType) || !expectedComment.equals(actualComment)) {
                    String sql = "ALTER TABLE " + tableName +
                            " MODIFY COLUMN " + columnName + " " + expectedType;
                    if (!expectedComment.isEmpty()) {
                        sql += " COMMENT '" + expectedComment + "'";
                    }
                    sql += ";";

                    jdbcTemplate.execute(sql);
                    logger.info("修改字段类型/注释====>{}", sql);
                }
            }
        }
    }

    /**
     * 获取数据库中已有的列名。
     *
     * @param tableName 表名
     * @return 列名集合
     */
    public Set<String> getTableColumns(String tableName) {
        String sql = "DESCRIBE " + tableName;
        List<String> columns = jdbcTemplate.query(sql, (rs, rowNum) -> rs.getString("Field"));
        Set<String> set = new HashSet<>();
        for (String s : columns) {
            set.add(s.toLowerCase());
        }
        return set;
    }

    /**
     * 获取数据库中已有字段类型。
     *
     * @param tableName 表名
     * @return 字段类型映射
     */
    public Map<String, String> getColumnTypes(String tableName) {
        String sql = "DESCRIBE " + tableName;
        Map<String, String> map = new HashMap<>();
        jdbcTemplate.query(sql, rs -> {
            String name = rs.getString("Field");
            String type = rs.getString("Type");
            map.put(name.toUpperCase(), type.toUpperCase());
        });
        return map;
    }

    /**
     * 获取数据库中已有字段注释。
     *
     * @param tableName 表名
     * @return 字段注释映射
     */
    public Map<String, String> getColumnComments(String tableName) {
        String sql = "SELECT COLUMN_NAME, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?";
        Map<String, String> map = new HashMap<>();
        jdbcTemplate.query(sql, rs -> {
            String name = rs.getString("COLUMN_NAME");
            String comment = rs.getString("COLUMN_COMMENT");
            map.put(name.toUpperCase(), comment);
        }, tableName);
        return map;
    }

    /**
     * 解析字段对应的列名。
     *
     * @param field 字段
     * @return 数据库列名
     */
    public String resolveColumnName(Field field) {
        TableField anno = field.getAnnotation(TableField.class);
        TableId annotation = field.getAnnotation(TableId.class);

        if (anno == null && annotation == null) return null;

        return camelToUnderline(field.getName());
    }

    /**
     * 解析字段对应的SQL类型。
     *
     * @param field 字段
     * @return SQL 类型字符串
     */
    public String resolveSqlType(Field field) {
        Class<?> type = field.getType();

        if (type.equals(String.class)) {
            Size sizeAnno = field.getAnnotation(Size.class);
            if (sizeAnno != null && sizeAnno.max() > 65535) {
                return "LONGTEXT";
            } else if (sizeAnno != null && sizeAnno.max() > 255) {
                return "TEXT";
            } else {
                return "VARCHAR(" + Math.min(sizeAnno == null ? 255 : sizeAnno.max(), 255) + ")";
            }
        }

        if (field.isAnnotationPresent(TableId.class)) {
            TableId idAnno = field.getAnnotation(TableId.class);
            if (idAnno.type() == IdType.AUTO) {
                return "BIGINT AUTO_INCREMENT PRIMARY KEY";
            } else {
                return "VARCHAR(32)";
            }
        }

        return SQL_TYPES.getOrDefault(type, "TEXT");
    }

    /**
     * 将驼峰命名转换为下划线命名。
     *
     * @param name 驼峰命名
     * @return 下划线命名
     */
    public String camelToUnderline(String name) {
        StringBuilder result = new StringBuilder();
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if (Character.isUpperCase(c)) {
                result.append("_").append(Character.toLowerCase(c));
            } else {
                result.append(c);
            }
        }
        return result.toString();
    }

    /**
     * 解析字段注释。
     *
     * @param field 字段
     * @return 字段注释内容
     */
    public String resolveColumnComment(Field field) {
        ApiModelProperty anno = field.getAnnotation(ApiModelProperty.class);
        return anno != null && !anno.value().isEmpty() ? anno.value() : "";
    }

    /**
     * 解析表注释。
     *
     * @param clazz 实体类
     * @return 表注释内容
     */
    public String resolveTableComment(Class<?> clazz) {
        ApiModel anno = clazz.getAnnotation(ApiModel.class);
        return anno != null && !anno.value().isEmpty() ? anno.value() : "";
    }
}